package com.wosai.medical.hospital.demo.controller;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.wosai.medical.hospital.demo.common.PayFeeException;
import com.wosai.medical.hospital.demo.service.AppSignService;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.util.Map;

/**
 * 控制器-基类
 *
 * @author yangyong
 * @version 1.0
 * @date 2019/11/23 2:58 下午
 **/
@Slf4j
public abstract class BaseController {
    /**
     * 注入-App签名-服务接口
     */
    @Autowired
    private AppSignService appSignService = null;

    /**
     * 业务处理
     *
     * @param process 业务处理进程
     * @param <T>     响应数据类型
     * @return 响应数据
     */
    protected <T extends Serializable> RespResult<T> action(@Nonnull final Process<T> process) {
        try {
            //检查参数
            Assert.hasText(process.getAppId(), "'api_app_id'不能为空!");
            Assert.notNull(process.getVersion(), "'api_version'不能为空!");
            Assert.notNull(process.getStamp(), "'api_stamp'不能为空!");
            Assert.hasText(process.getSign(), "'api_sign'不能为空!");
            //获取参数签名
            final String sign = process.getSign();
            //构建参数签名
            final String newSign = appSignService.buildSign(buildArguments(process));
            //检查参数数据
            if (Strings.isNullOrEmpty(sign) || !sign.equalsIgnoreCase(newSign)) {
                return buildResult(RespResultStatus.SignError, "校验参数签名错误!", null);
            }
            //业务处理
            return buildSuccess(process.handler());
        } catch (Throwable ex) {
            return buildFail(ex);
        }
    }

    private <T extends Serializable> Map<String, Serializable> buildArguments(@Nonnull final Process<T> process) {
        //初始化参数
        final Map<String, Serializable> params = Maps.newHashMap();
        //检查参数
        final Map<String, Serializable> maps;
        if (!CollectionUtils.isEmpty(maps = process.getParams())) {
            params.putAll(maps);
        }
        //接入账号
        params.put("api_app_id", process.getAppId());
        //接口版本号(采用正整数描述)
        params.put("api_version", process.getVersion());
        //时间戳(13位到毫秒)
        params.put("api_stamp", process.getStamp());
        return params;
    }

    /**
     * 构建响应结果
     *
     * @param status 响应状态
     * @param msg    响应消息
     * @param data   响应数据
     * @param <T>    响应数据类型
     * @return 响应结果
     */
    private static <T extends Serializable> RespResult<T> buildResult(@NonNull final RespResultStatus status, @Nullable final String msg, @Nullable final T data) {
        return RespResult.<T>builder()
                //状态码(0:成功,非0:失败)
                .code(status.getVal())
                //提示消息
                .msg(Strings.isNullOrEmpty(msg) ? status.getTitle() : msg)
                //响应数据
                .data(data)
                .build();
    }

    /**
     * 构建成功响应结果
     *
     * @param data 响应数据
     * @param <T>  响应数据类型
     * @return 响应结果
     */
    private static <T extends Serializable> RespResult<T> buildSuccess(@Nonnull final T data) {
        return buildResult(RespResultStatus.Success, null, data);
    }

    /**
     * 构造失败响应结果
     *
     * @param ex  异常消息
     * @param <T> 响应数据类型
     * @return 响应结果
     */
    private static <T extends Serializable> RespResult<T> buildFail(@Nonnull final Throwable ex) {
        if (ex instanceof PayFeeException) {
            PayFeeException payFeeException = (PayFeeException) ex;
            return buildPayFail(payFeeException);
        }
        return buildResult(RespResultStatus.Fail, ex.getMessage(), null);
    }

    /**
     * 缴费异常，需要平台退费
     *
     * @param ex
     * @param <T>
     * @return
     */
    private static <T extends Serializable> RespResult<T> buildPayFail(@Nonnull final PayFeeException ex) {
        return buildResult(RespResultStatus.RetrunFee, ex.getMessage(), null);
    }

    /**
     * 响应结果
     *
     * @param <T> 业务数据类型
     */
    @Data
    @Builder
    @ApiModel("响应结果")
    @NoArgsConstructor
    @AllArgsConstructor
    @JsonInclude(JsonInclude.Include.NON_NULL)
    protected static class RespResult<T extends Serializable> implements Serializable {
        /**
         * 状态码(0:成功,非0:失败)
         */
        @ApiModelProperty(value = "状态码")
        private Integer code;
        /**
         * 提示消息
         */
        @ApiModelProperty("提示消息")
        private String msg;
        /**
         * 响应数据
         */
        @ApiModelProperty("响应数据")
        private T data;
    }

    /**
     * 响应状态代码枚举
     */
    @Getter
    public enum RespResultStatus {
        /**
         * 成功
         */
        Success(0, "成功"),
        /**
         * 错误
         */
        Fail(-1, "错误"),

        /**
         * 错误
         */
        RetrunFee(-2, "缴费错误，平台需要退费"),
        /**
         * 接入账号不存在
         */
        AccessAccountNotExit(401, "接入账号不存在"),
        /**
         * 签名错误
         */
        SignError(402, "签名错误");

        private final int val;
        private final String title;

        RespResultStatus(final int val, final String title) {
            this.val = val;
            this.title = title;
        }
    }

    /**
     * 业务处理
     */
    public interface Process<T extends Serializable> {
        /**
         * 获取接入账号
         *
         * @return 接入账号
         */
        String getAppId();

        /**
         * 获取版本号
         *
         * @return 版本号
         */
        Integer getVersion();

        /**
         * 获取时间戳
         *
         * @return 时间戳
         */
        Long getStamp();

        /**
         * 获取参数签名
         *
         * @return 参数签名
         */
        String getSign();

        /**
         * 获取参数键值集合
         *
         * @return 参数键值集合
         */
        default Map<String, Serializable> getParams() {
            return null;
        }

        /**
         * 业务处理
         *
         * @return 处理结果
         */
        T handler();
    }
}